package cn.ywang.spring.framework.aop.framework;

import cn.ywang.spring.framework.aop.config.AopConfig;
import cn.ywang.spring.framework.aop.framework.adapter.AfterReturningAdviceInterceptor;
import cn.ywang.spring.framework.aop.framework.adapter.AfterThrowingAdviceInterceptor;
import cn.ywang.spring.framework.aop.intercept.MethodBeforeAdviceInterceptor;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author BeatificWang
 * @date 2019/5/4 16:12
 */
public class AdvisedSupport {

	private Class<?> targetClass;

	private Object target;

	private AopConfig config;

	private Pattern pointCutClassPattern;

	private transient Map<Method, List<Object>> methodCache;

	public AdvisedSupport(AopConfig config) {
		this.config = config;
	}

	public Class<?> getTargetClass() {
		return this.targetClass;
	}

	public Object getTarget() {
		return this.target;
	}

	public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) throws Exception {
		List<Object> cached = this.methodCache.get(method);
		if (cached == null) {
			Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());

			cached = this.methodCache.get(m);

			//底层逻辑，对代理方法进行一个兼容处理
			this.methodCache.put(m, cached);
		}

		return cached;
	}

	public void setTargetClass(Class<?> targetClass) {
		this.targetClass = targetClass;
		this.parse();
	}

	private void parse() {
		String pointCut = this.config.getPointCut()
				                  .replaceAll("\\.", "\\\\.")
				                  .replaceAll("\\\\.\\*", ".*")
				                  .replaceAll("\\(", "\\\\(")
				                  .replaceAll("\\)", "\\\\)");
		//pointCut=public .* com.ywang.spring.demo.service..*Service..*(.*)
		String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
		this.pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(
				pointCutForClassRegex.lastIndexOf(" ") + 1));

		try {

			this.methodCache = new HashMap<Method, List<Object>>();
			Pattern pattern = Pattern.compile(pointCut);


			Class aspectClass = Class.forName(this.config.getAspectClass());
			Map<String, Method> aspectMethods = new HashMap<String, Method>();
			for (Method m : aspectClass.getMethods()) {
				aspectMethods.put(m.getName(), m);
			}

			for (Method m : this.targetClass.getMethods()) {
				String methodString = m.toString();
				if (methodString.contains("throws")) {
					methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();
				}

				Matcher matcher = pattern.matcher(methodString);
				if (matcher.matches()) {
					//执行器链
					List<Object> advices = new LinkedList<>();
					//把每一个方法包装成 MethodInterceptor
					//before
					if (!(null == this.config.getAspectBefore() || "".equals(this.config.getAspectBefore()))) {
						//创建一个Advice
						advices.add(new MethodBeforeAdviceInterceptor(aspectMethods.get(this.config.getAspectBefore()), aspectClass.newInstance()));
					}
					//after
					if (!(null == this.config.getAspectAfter() || "".equals(this.config.getAspectAfter()))) {
						//创建一个Advice
						advices.add(new AfterReturningAdviceInterceptor(aspectMethods.get(this.config.getAspectAfter()), aspectClass.newInstance()));
					}
					//afterThrowing
					if (!(null == this.config.getAspectAfterThrow() || "".equals(this.config.getAspectAfterThrow()))) {
						//创建一个Advice
						AfterThrowingAdviceInterceptor throwingAdvice =
								new AfterThrowingAdviceInterceptor(
										aspectMethods.get(this.config.getAspectAfterThrow()),
										aspectClass.newInstance()
								);
						throwingAdvice.setThrowName(this.config.getAspectAfterThrowingName());
						advices.add(throwingAdvice);
					}
					this.methodCache.put(m, advices);
				}

			}
		} catch (Exception e) {
			e.printStackTrace();
		}


	}

	public void setTarget(Object target) {
		this.target = target;
	}

	public boolean pointCutMatch() {
		return this.pointCutClassPattern.matcher(this.targetClass.toString()).matches();
	}

}
